package com.chenfan.magic.service.impl;

import com.alibaba.druid.pool.DruidDataSource;
import com.chenfan.magic.constant.RuntimeConstant;
import com.chenfan.magic.entity.IdcDatasource;
import com.chenfan.magic.entity.IdcDatasourceType;
import com.chenfan.magic.entity.Isv;
import com.chenfan.magic.service.IDatasourceCensusService;
import com.chenfan.magic.utils.MD5;
import com.chenfan.magic.utils.StringKit;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Map;

/**
 * @author lm
 * 数据源池
 */
@Slf4j
@Component
public class DataSourcePool {

    @Autowired
    private IDatasourceCensusService datasourceCensusService;

    @Autowired
    private StatLoggerGather statLoggerGather;

    private static volatile Map<String, DruidDataSource> map = Maps.newHashMap();

    public static final String NO_GATHER_MONITOR_ = "_NO_GATHER_MONITOR_";
    public static final String NO_GATHER_CENSUS_ = "_NO_GATHER_CENSUS_";

    /**
     * 删除数据源 [synchronized]
     *
     * @param jdbcUrl
     * @param username
     * @param password
     */
    public synchronized void removeDatasource(String jdbcUrl, String username, String password) {
        String key = getKey(jdbcUrl, username, password);
        if (map.containsKey(key)) {
            map.remove(key);
        }
    }

    /**
     * 分配数据源 [synchronized]
     *
     * @param idcDatasourceType
     * @param idcDatasource
     * @return
     * @throws Exception
     */
    public synchronized DruidDataSource getDataSource(Isv isv, IdcDatasourceType idcDatasourceType, IdcDatasource idcDatasource) throws Exception {
        String jdbcUrl = idcDatasource.getUrlDecrypt(isv.getAppSecret());
        String username = idcDatasource.getUsername();
        String password = idcDatasource.getPasswordDecrypt(isv.getAppSecret());
        String key = getKey(jdbcUrl, username, password);
        DruidDataSource instance = null;
        if (!map.containsKey(key) || null == map.get(key)) {
            instance = new DruidDataSource();
            String className = null;
            try {
                className = DriverManager.getDriver(jdbcUrl.trim()).getClass().getName();
            } catch (SQLException e) {
            }
            if (StringKit.isEmpty(className)) {
                throw new Exception("Driver Class Not null: DbId=" + idcDatasource.getId());
            } else {
                instance.setDriverClassName(className);
            }
            instance.setName(idcDatasource.getName() + " [MD5-> " + key + "]"
                    + (1 != idcDatasource.getGatherMonitor() ? "  " + NO_GATHER_MONITOR_ : "")
                    + (1 != idcDatasource.getGatherMonitor() ? "  " + NO_GATHER_CENSUS_ : ""));
            instance.setDbType(idcDatasourceType.getDbType());
            instance.setUrl(jdbcUrl);
            instance.setUsername(username);
            instance.setPassword(password);
            instance.setDriverClassName(idcDatasourceType.getDriverClassName());
            /** configuration */
            instance.setInitialSize(idcDatasource.getInitialSize());
            instance.setMinIdle(idcDatasource.getMinIdle());
            instance.setMaxActive(idcDatasource.getMaxActive());
            instance.setMaxWait(idcDatasource.getMaxWait());
            instance.setTimeBetweenEvictionRunsMillis(idcDatasource.getTimeBetweenEvictionRunsMillis());
            instance.setMinEvictableIdleTimeMillis(idcDatasource.getMinEvictableIdleTimeMillis());
            instance.setValidationQuery(idcDatasource.getValidationQuery());
            instance.setTestWhileIdle("Y".equals(idcDatasource.getTestWhileIdle()));
            instance.setTestOnBorrow("Y".equals(idcDatasource.getTestOnBorrow()));
            instance.setTestOnReturn("Y".equals(idcDatasource.getTestOnReturn()));
            instance.setPoolPreparedStatements("Y".equals(idcDatasource.getPoolPreparedStatements()));
            instance.setMaxPoolPreparedStatementPerConnectionSize(idcDatasource.getMaxPoolPreparedStatementPerConnectionSize());
            instance.setConnectionErrorRetryAttempts(idcDatasource.getConnectionErrorRetryAttempts());
            instance.setBreakAfterAcquireFailure("Y".equals(idcDatasource.getBreakAfterAcquireFailure()));
            instance.setKeepAlive("Y".equals(idcDatasource.getKeepAlive()));
            instance.setAsyncInit("Y".equals(idcDatasource.getAsyncInit()));
            instance.setTimeBetweenConnectErrorMillis(idcDatasource.getTimeBetweenConnectErrorMillis());

            //收集监控信息的开关
            if (1 == idcDatasource.getGatherMonitor()) {
                instance.setTimeBetweenLogStatsMillis(idcDatasource.getTimeBetweenLogStatsMillis());
                instance.setStatLogger(statLoggerGather);
            }
            try {
                instance.setFilters(idcDatasource.getFilters());
            } catch (SQLException e) {
                log.error("druid configuration initialization filter", e);
            }
            instance.setConnectionProperties(idcDatasource.getConnectionProperties());

            try {
                instance.init();
            } catch (Exception e) {
                log.error("Exception during pool initialization", e);
                throw new Exception(e.getMessage());
            }
            map.put(key, instance);
        }
        instance = map.get(key);
        //统计数据源使用信息
        if (!instance.getName().contains(NO_GATHER_CENSUS_)) {
            datasourceCensusService.record(idcDatasource.getId());
        }
        log.info("切换数据源: " + instance.getName());
        log.info("数据源池中池大小: " + map.size());
        return instance;
    }


    public synchronized DruidDataSource getDataSource(Map<String, Object> idcRuntimePool, IdcDatasourceType idcDatasourceType, IdcDatasource idcDatasource) throws Exception {
        Isv isv = (Isv) idcRuntimePool.get(RuntimeConstant.RUNTIME_ISV);
        return getDataSource(isv, idcDatasourceType, idcDatasource);
    }

    /**
     * 生成一个 Key, 用于获取数据源
     *
     * @param jdbcUrl
     * @param username
     * @param password
     * @return
     */
    private String getKey(String jdbcUrl, String username, String password) {
        StringBuilder sb = new StringBuilder();
        if (!StringKit.isEmpty(username)) {
            sb.append(username);
        }
        if (!StringKit.isEmpty(password)) {
            sb.append(":").append(password);
        }
        sb.append("@").append(jdbcUrl.trim());
        return MD5.encrypt(sb.toString());
    }
}
